/********************************************************************
 * Copyright (c) 2006 Contributors. All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors: IBM Corporation - initial API and implementation
 * 				 Helen Hawkins   - initial version
 *******************************************************************/
package org.aspectj.systemtest.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.aspectj.asm.AsmManager;
import org.aspectj.asm.IElementHandleProvider;
import org.aspectj.asm.IModelFilter;
import org.aspectj.testing.XMLBasedAjcTestCase;
import org.aspectj.util.FileUtil;

/**
 * This class provides an extension to the XMLBasedAjcTestCase to manage testing the model. It assumes the testdata is in
 * ../tests/model/<testid> and that the expected model against which to do the comparison is in the file
 * ../tests/model/expected/<testid>.txt. One test ensures that both the model and the relationship map are as expected for the given
 * testdata.
 *
 * To write a testcase, create a testdata directory containing the data for the test run and a file containing the expected model
 * (this can be generated by setting the regenerate flag to true). Add the required configuration to model.xml. Finally, create a
 * testcase in either ModelTests or Model5Tests (depending on whether the testcase has a requirement on Java5) and call
 * runModelTest(<title of test>,<testid>).
 */
public abstract class ModelTestCase extends XMLBasedAjcTestCase {

	protected static boolean regenerate = false;
	protected static boolean debugTest = false;

	private final String expectedOutDir = "../tests/model/expected" + File.separator;
	private String testid;

	private String modelFilename;

	private IElementHandleProvider handleProvider;

	/*
	 * (non-Javadoc)
	 *
	 * @see junit.framework.TestCase#setUp()
	 */
	protected void setUp() throws Exception {
		super.setUp();
		// using the JDTLikeHandleProvider because this produces consistent handles
		// over different compiles
		// We are about to create a sandbox for the model output file, don't let the
		// following compile wipe it.
		ajc.setShouldEmptySandbox(false);
		// report all information - model, relationships delta processing
		modelFilename = ajc.getSandboxDirectory().getAbsolutePath() + File.separator + "model.txt";
		AsmManager.setReporting(modelFilename, true, true, true, false,
				new TestFilter(ajc.getSandboxDirectory().getCanonicalPath()));
	}

	static class TestFilter implements IModelFilter {
		String sandboxDirectory;

		public TestFilter(String sandboxDirectory) {
			this.sandboxDirectory = sandboxDirectory;
		}

		public String processFilelocation(String loc) {
			if (loc.toLowerCase().startsWith(sandboxDirectory.toLowerCase())) {
				String sub = loc.substring(sandboxDirectory.length());
				int forwardSlash = sub.indexOf("/");
				// replace all "/" with "\" - to ensure platform independence
				if (forwardSlash != -1) {
					sub = sub.replace('/', '\\');
				}
				// don't report the column number since this is sometimes
				// different on windows and linux
				int column = sub.lastIndexOf(':');
				if (column != -1) {
					return "TEST_SANDBOX" + sub.substring(0, column);
				}
				return "TEST_SANDBOX" + sub;
			}
			return loc;
		}

		public boolean wantsHandleIds() {
			return false;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see junit.framework.TestCase#tearDown()
	 */
	protected void tearDown() throws Exception {
		super.tearDown();
		AsmManager.setDontReport();
		ajc.setShouldEmptySandbox(true);
	}

	/**
	 * Firstly sets the testid which is both the name of the expected output file and the name of the testdata directory. It then
	 * invokes XMLBasedAjcTestCase.runTest(String) with the given title and finally verifies that the model file created from this
	 * test run is the same as the expected output (includes model information, the relationship map and various properties about
	 * the model) contained in ../tests/model/expected/<testid>.txt
	 */
	protected void runModelTest(String title, String testid) {
		this.testid = testid;
		runTest(title);
		verifyModel();
	}

	private void verifyModel() {
		File expectedOutput = new File(expectedOutDir + testid + ".txt");
		if (regenerate) {
			// Create the file
			saveModel(expectedOutput);
		} else {
			// Verify the file matches what we have
			compareModel(expectedOutput);
		}
	}

	private void compareModel(File expectedF) {
		if (debugTest)
			System.out.println("comparing with model in file " + expectedF.getAbsolutePath());
		List<String> fileContents = new ArrayList<>();
		try {
			// String sandboxDir = ajc.getSandboxDirectory().getAbsolutePath();
			String modelOutput = modelFilename;
			// Load the file with the expected output
			BufferedReader expect = new BufferedReader(new FileReader(expectedF));
			// String tempDir = expect.readLine();
			String expectedLine = null;
			while ((expectedLine = expect.readLine()) != null) {
				// Remove trailing whitespace
				expectedLine = expectedLine.replaceAll("[\t ]+$", "");
				fileContents.add(expectedLine);
			}
			List<String> expectedFileContents = new ArrayList<>(fileContents);

			// Load the file with the output from this test run
			BufferedReader found = new BufferedReader(new FileReader(new File(modelOutput)));
			String foundLine = null;
			List<String> foundFileContents = new ArrayList<>();
			while ((foundLine = found.readLine()) != null) {
				// Remove trailing whitespace
				foundLine = foundLine.replaceAll("[\t ]+$", "");
				// int i = foundLine.indexOf(sandboxDir);
				// if (i == -1) {
				// int j = foundLine.indexOf("(targets=");
				// if (j == -1) {
				foundFileContents.add(foundLine);
				// } else {
				// foundFileContents.add(foundLine.substring(j));
				// }
				// } else {
				// String newLine = foundLine.substring(0,i) + tempDir
				// + foundLine.substring(i + sandboxDir.length());
				// foundFileContents.add(newLine);
				// }
			}

			// iterate over what we found
			for (String line : foundFileContents) {
				if (debugTest)
					System.err.println("looking at model entry: " + line);
				if (!fileContents.contains(line)) {
					// if (!((String)fileContents.get(lineNumber)).equals(line)) {

					if (debugTest) {
						System.err.println("couldn't find: " + line);
						for (String element : fileContents) {
							System.err.println("compared with: " + element);
						}
					}

					// StringBuffer errorData = new StringBuffer();
					// errorData.append("Problem with comparison at line number: "+)
					fail("couldn't find model entry '" + line + "' in expected output");
				} else {
					fileContents.remove(line);
				}
			}

			if (debugTest && !fileContents.isEmpty()) {
				for (String element : fileContents) {
					System.err.println("remaining: " + element);
				}
			}
			assertTrue("should have found all expected model output: " + fileContents, fileContents.isEmpty());
		} catch (Exception e) {
			fail("Unexpected exception comparing model files:" + e);
		}
	}

	private void saveModel(File f) {
		if (debugTest)
			System.out.println("Saving model into " + f.getAbsolutePath());
		File modelFile = new File(modelFilename);
		try {
			FileUtil.copyFile(modelFile, f);
		} catch (IOException ioe) {
			ioe.printStackTrace();
			fail("Couldn't copy file to " + f.toString());
		}
	}

}
